// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#include "coin2/exchange/base/symbology/util.h"

#include "coin/proto/symbology.pb.h"
#include "coin2/base/log.h"
#include "coin2/base/proto_util.h"
#include "coin2/base/string_util.h"
#include <google/protobuf/util/json_util.h>

namespace coin2::exchange::base::symbology {

ProductInfoConstant::ProductInfoConstant(const std::experimental::filesystem::path& filename)
    : product_infos_(::new coin::proto::ProductInfoProtoBundle{}) {
  if (filename.empty()) {
    LOG(ERROR) << "Empty product info filename: " << filename;
  }
  auto content = ReadFileContent(filename);
  if (content.empty()) {
    LOG(ERROR) << "Empty product info: " << filename;
  } else {
    using google::protobuf::util::JsonParseOptions;
    using google::protobuf::util::JsonStringToMessage;
    JsonParseOptions options{};
    options.ignore_unknown_fields = true;
    if (const auto ret = JsonStringToMessage(content, product_infos_.get(), options); !ret.ok()) {
      // LOG(ERROR) << status;
    }
  }

  for (auto& pi : *(product_infos_->mutable_product_infos())) {
    pi_map_[pi.symbol()] = &pi;
    pi_map_native_[pi.native_symbol()] = &pi;
    native_currencies_.insert(pi.native_base());
    native_currencies_.insert(pi.native_quote());
    norm_currencies_.insert(pi.base());
    norm_currencies_.insert(pi.quote());
    native_products_.insert(pi.native_symbol());
  }
}

ProductInfoConstant::~ProductInfoConstant() = default;

// Merge 2 lists into 1 longer list.
void ProductInfoConstant::MergeFrom(const ProductInfoConstant& pi_const) {
  if (product_infos_ == nullptr) {
    product_infos_.reset(new coin::proto::ProductInfoProtoBundle);
  }
  product_infos_->mutable_product_infos()->MergeFrom(pi_const.GetProductInfoBundle().product_infos());

  for (auto& pi : *(product_infos_->mutable_product_infos())) {
    pi_map_[pi.symbol()] = &pi;
    pi_map_native_[pi.native_symbol()] = &pi;
    native_currencies_.insert(pi.native_base());
    native_currencies_.insert(pi.native_quote());
    norm_currencies_.insert(pi.base());
    norm_currencies_.insert(pi.quote());
    native_products_.insert(pi.native_symbol());
  }
}

// Merge addtional infos from another pi_const.
// Additional Info:
//   1. list_time
//   2. capture_time
void ProductInfoConstant::PartialMergeFrom(const ProductInfoConstant& pi_const) {
  for (auto& pi : pi_const.GetProductInfoBundle().product_infos()) {
    DCHECK(pi.has_list_time());
    DCHECK(pi.has_capture_time());
    if (pi_map_.count(pi.symbol()) > 0) {
      pi_map_[pi.symbol()]->set_list_time(pi.list_time());
      pi_map_[pi.symbol()]->set_capture_time(pi.capture_time());
    } else {
      LOG(WARNING) << "Extra pi's symbol not in main pi-const";
    }
  }
}

void ProductInfoConstant::MergeExtraFrom(const ProductInfoConstant& pi_const) {
  for (auto& pi : pi_const.GetProductInfoBundle().product_infos()) {
    if (pi_map_.count(pi.symbol()) > 0) continue;
    auto new_pi = product_infos_->add_product_infos();
    new_pi->MergeFrom(pi);

    pi_map_[new_pi->symbol()] = new_pi;
    pi_map_native_[new_pi->native_symbol()] = new_pi;
    native_currencies_.insert(new_pi->native_base());
    native_currencies_.insert(new_pi->native_quote());
    norm_currencies_.insert(new_pi->base());
    norm_currencies_.insert(new_pi->quote());
    native_products_.insert(new_pi->native_symbol());
  }
}

const coin::proto::ProductInfoProto& ProductInfoConstant::GetProductInfo(
    const std::string& name) const {
  if (pi_map_.find(name) == pi_map_.end()) {
    throw std::runtime_error("ProductInfo has no symbol " + name);
  }
  return *(pi_map_.at(name));
}

const coin::proto::ProductInfoProtoBundle& ProductInfoConstant::GetProductInfoBundle() const {
  return *product_infos_;
}

const coin::proto::ProductInfoProto& ProductInfoConstant::GetProductInfoByNativeSymbol(
    const std::string& name) const {
  return *(pi_map_native_.at(name));
}

bool ProductInfoConstant::HasNativeProduct(const std::string& native_product) const {
  return native_products_.count(native_product) == 1;
}

bool ProductInfoConstant::HasNativeCurrency(const std::string& native_currency) const {
  return native_currencies_.count(native_currency) == 1;
}

bool ProductInfoConstant::HasNormCurrency(const std::string& norm_currency) const {
  return norm_currencies_.count(norm_currency) == 1;
}

}  // namespace coin2::exchange::base::symbology
